/**
 *  Copyright (C) 2007 Marcos Diez < marcos AT unitron.com.br >
 *  Copyright (C) 2005 PGT-Linux.org http://www.pgt-linux.org
 *  Author: vandorpe Olivier <vandorpeo@pgt-linux.org>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <usb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <pthread.h>
#include <syslog.h>
#include <sys/stat.h>
#include <signal.h>
#include <time.h>

#include "usbb2k-main.h"
#include "usbb2k-api.h"
#include "usbTalker.h"
#include "fifoApi.h"
#include "yealinkProtocol.h"
#include "usbServer.h"
#include "cmdServer.h"

#define USBB2K_SOCKET "/tmp/usbb2k.sock"
#define USBB2K_PID    "/var/run/usbb2k_api.pid"

#define PID_MAXL 1000

// state variables

int ringing_state=OFF; /* RING LOOP ON/OFF */
int ringing_mode=OFF; /* RINGING MODE ON/OFF */
int handset_state=OFF; /* HANDSET STATE ON/OFF */
int dialtone=OFF;

int cmdServer_loop=OFF;
int usb_loop=OFF;
int main_loop=OFF;

// --- added by chris at lunarflow.com 05/08/2008 ---
int ringing_sendcid=OFF; /* RINGING MODE ON/OFF */

// ---

static yealinkDevice_t yealinkDevice;

static void closeIO(yealinkDevice_t *yealinkDevice) {
	if (yealinkDevice->socketFile) {
		fclose(yealinkDevice->socketFile);
		yealinkDevice->socketFile=0;
	}
	if (yealinkDevice->socketFD) {
		close(yealinkDevice->socketFD);
		yealinkDevice->socketFD=0;
	}
}


static void signal_handler(int signal) {
	switch (signal) {
	case SIGHUP:
		syslog(LOG_INFO, "SIGHUP[%d] received" , signal);
		break;
	case SIGINT:
	case SIGTERM:
		syslog(LOG_INFO, "SIG [%d] received. Quitting", signal);
		fifoWrite( &yealinkDevice, "%s", FIFO_SAY_DAEMON_SHUTDOWN);
		cmdServer_loop=OFF;
		usb_loop=OFF;
		main_loop=OFF;
		closelog();
		
		sleep(1);
		if (yealinkDevice.socketName) {
			unlink(yealinkDevice.socketName);
		}
		closeIO(&yealinkDevice);
		exit(1); // just to make sure!
		break;
	default:
		syslog(LOG_INFO, "SIG [%d] received.", signal);
		break;
	}
}

static void initiateLogging(struct myOptions *opts) {
	char pid_file[PID_MAXL];
	char pid_str[10];
	int fic;

	/* Syslog */
	if (opts->foregroundMode) {
		openlog("usbb2k_api", LOG_PID | LOG_PERROR, LOG_DAEMON);
	} else {
		openlog("usbb2k_api", LOG_PID, LOG_DAEMON);
	}
	syslog(LOG_INFO, "usbb2k/b3g driver %.2f started" , USBB2K_API_VERSION);
	// setlogmask(     LOG_EMERG | LOG_ALERT | LOG_CRIT | LOG_ERR | LOG_WARNING | LOG_NOTICE | LOG_INFO| LOG_DEBUG );
	/* the process don't create executable file */
	umask(0227);

	if ( !opts->halMode) { // no PID for HAL mode
		sprintf(pid_file, "%s", USBB2K_PID);
		/* Check if a other usbb2k_api process Already run */
		fic=open(pid_file, O_RDWR|O_CREAT, 0640);
		if (fic<0) {
			syslog(LOG_ERR, "open PID file failed");
			exit(-1);
		}

		if (lockf(fic, F_TLOCK, 0)<0) {
			syslog(LOG_ERR, "lockf failed");
			exit(-1);
		}
		sprintf(pid_str, "%d", getpid());
		write(fic, pid_str, strlen(pid_str));
	}
	/* Signal catch */
	signal(SIGHUP, signal_handler);
	signal(SIGTERM, signal_handler);
	signal(SIGINT, signal_handler);

}


static void daemon_mode() { /*  DAEMON MODE */
	int i;
	int pid;

	/* Fork process */
	pid=fork();
	if (pid<0) {
		perror("error on fork");
		exit(-1);
	}
	if (pid>0) {
		exit(0); // we close the main thread, child(daemon) continues
	}

	if (setsid()<0) {
		perror("echec setsid");
		exit(-1);
	}

	for (i=getdtablesize()-1; i>=0; --i) {
		close(i);
	}
	/* flux stdin */
	i=open("/dev/null", O_RDWR);
	/* flux stdout */
	dup(i);
	/* flux stderr */
	dup(i);

}

/* normal workflow:
 * 
 * USBB2K_API is executed
 * socket is created
 * program sleeps
 * whenever an external app open()s the socket, USBB2K_API searches for a Yealink device and connects to it
 * if device is disconnected or external app close()s the socket, program USBB2K_API again 

 */

/*
 *  -H hal mode (bind an specific USB host instead of all of them, 
 * 				 drop privileges after binding)
 * 				getenv( "HAL_PROP_USB_BUS_NUMBER" ); 
 *  -f foreground mode LOG_CONS
 *  -h help
 */

/* hal mode workflow:
 *
 * USER inserts an Yealink Device
 * HAL launches USBB2k_API
 * 
 * USBB2K_API connects to the Yealink device
 *            checks if model is supported (b2k and b3g) and parses its serial number
 * USBB2K_API closes the USB device (saves resources with useless USB pooling)
 * 			  if model is invalid, USBB2K_API quits and logs why it did so
 * USBB2K_API creates the SOCKET on /tmp/usbb2k_MODEL_SERIAL_USBPORT.sock (multiple devices are now supported)
 * if USBB2K_SOCKET does not exist, USBB2K_SOCKET will be created as a link which points to the previous created file.
 * 
 * whenever an application opens the socket, USBB2K_API will connect to the device again
 * whenever an application closes the socket, USBB2K_API will disconnect to the device
 * 
 * if the device is removed, USBB2K will receive SIGTERM from HAL and quit.
 * if the socket was opened, FIFO_SAY_DEVICE_REMOVED will be send to the socket, rightafter it will be closed.
 * 
 */

static void usage(char *myName) {
	printf(
			"Yealink B2K/B3G %f driver, now manteined by Marcos Diez <marcos AT unitron.com.br>\n", 
			USBB2K_API_VERSION);
	printf("usage: %s [-H] [-h] [-f]\n\n", myName);
	printf("\t-H\tHAL mode\n");
	printf("\t-h\tdisplay help\n");
	printf("\t-f\tforeground (debug) mode \n");
	putchar('\n');
	printf(
			"if not in HAL mode, the daemon will create a socket on %s and when some software open the socket,"
				"search for USB B2K and USB B3G devices on every USB bus, stopping as soon as it finds one.\n", 
			USBB2K_SOCKET);
	printf("in HAL mode, it will just try to connect with the device on bus $HAL_PROP_USB_BUS_NUMBER, device $HAL_PROP_USB_LINUX_DEVICE_NUMBER.\n"
		"the socket name will be different, so that multiple instances of this daemon may be running at the same time.\n\n");

}

static void parse_args(int argc, char **argv, struct myOptions *opts) {

	int arg_index;
	// default values
	opts->halMode=0;
	opts->foregroundMode=0;

	while ( 42) {
		arg_index = getopt(argc, argv, "hHf");

		if (arg_index == -1) {
			break;
		}
		switch (arg_index) {

		case 'h':
			usage(argv[0]);
			exit(0);
			continue;
		case 'H':
			opts->halMode=1;
			continue;
		case 'f':
			opts->foregroundMode=1;
			continue;
		default:
			printf("ERROR: invalid options\n");
			usage(argv[0]);
			exit(EXIT_FAILURE);
		}
	}
}

/* connects to the yealink device a first time, to get the serial number
 * it only checks on USB port $HAL_PROP_USB_BUS_NUMBER, which should be given by HAL
 */
static void halModeInit(yealinkDevice_t *yealinkDevice) {

	syslog(LOG_ERR,
	"ERROR: HAL mode not yet stable enought. Exiting....");
	exit(3);

	sleep(5);
	char *usbBusName = getenv("HAL_PROP_USB_BUS_NUMBER");
	if (usbBusName == 0) {
		syslog(LOG_ERR,
		"ERROR: $HAL_PROP_USB_BUS_NUMBER does not exist or null");
		exit(1);
	}
	int usbBusNumber;
	if (sscanf(usbBusName, "%d", &usbBusNumber) != 1) {
		syslog(LOG_ERR,
		"ERROR: $HAL_PROP_USB_BUS_NUMBER has a non integer value");
		exit(1);
	}

	char *usbDeviceName= getenv("HAL_PROP_USB_LINUX_DEVICE_NUMBER");
	if (usbDeviceName == 0) {
		syslog(LOG_ERR,
		"ERROR: $HAL_PROP_USB_LINUX_DEVICE_NUMBER does not exist or null");
		exit(1);
	}
	int usbDeviceNumber;
	if (sscanf(usbDeviceName, "%d", &usbDeviceNumber) != 1) {
		syslog(LOG_ERR,
		"ERROR: $HAL_PROP_USB_LINUX_DEVICE_NUMBER has a non integer value");
		exit(1);
	}

	int claimed= claimYealinkDevice(yealinkDevice, usbBusNumber , usbDeviceNumber );

	if (claimed != YEALINK_DEVICE_SUCESSIFULLY_INITIALIZED) {
		syslog(LOG_ERR, "Error %d initializing the Yealink device on bus %d\n" , claimed , usbBusNumber);
		exit(3);
	}
	char *deviceName;
	yld_decode_model(yealinkDevice->deviceVersion, &deviceName);
	syslog(LOG_INFO, "Yealink device %s initialized on bus %d\n" , deviceName , usbBusNumber);

	releaseDevice(yealinkDevice);;

	createYealinkDeviceName(yealinkDevice);

}

int main(int argc, char *argv[]) {
	
	yealinkDevice.serialNumber[SERIAL_NUMBER_SIZE] = 0;
	yealinkDevice.socketFD = 0;
	yealinkDevice.socketName=0;
	yealinkDevice.dev_h=0;
	
	parse_args(argc, argv, &yealinkDevice.options);

	if (getuid() != 0) {
		printf(
				"%s ERROR: you must be root to launch this daemon. (usage: -h)\n",
				argv[0]);
		return 1;
	}
	usbTalkerInit();
	
	if ( !yealinkDevice.options.foregroundMode) {
		daemon_mode();
	}
	initiateLogging( &yealinkDevice.options); // daemon mode should not close the log... 
	if (yealinkDevice.options.halMode) {
		halModeInit( &yealinkDevice);
	} else {
		yealinkDevice.socketName=USBB2K_SOCKET;
	}

	int listener, len, t;

	//	char msg[MSG_MAXL];
	struct sockaddr_un server, client;
	//	struct usb_device* device;

	pthread_t usb_server_thread;
	int ret;

	// INIT UNIX_SOCKET
	if ((listener=socket(AF_UNIX, SOCK_STREAM, 0))==-1) {
		syslog(LOG_ERR, "socket");
		exit(1);
	}
	server.sun_family=AF_UNIX;
	strcpy(server.sun_path, yealinkDevice.socketName);
	unlink(server.sun_path);
	len = strlen(server.sun_path)+sizeof(server.sun_family);
	if (bind(listener, (struct sockaddr *)&server, len) == -1) {
		syslog(LOG_ERR, "bind");
		exit(1);
	}

	//##########################################################################
	//set permissions on the socket so normal users have access
	chmod(yealinkDevice.socketName, 
	S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP |S_IROTH|S_IWOTH);
	//###########################################################################

	if (listen(listener, 1)==-1) {
		syslog(LOG_ERR, "listen");
		exit(1);
	}

	t = sizeof(client);

	main_loop=1;

	do {
		if ((yealinkDevice.socketFD =accept(listener, (struct sockaddr*)&client,
				( socklen_t * )&t)) ==-1) {
			perror("accept");
			exit(1);
		}
		if( (yealinkDevice.socketFile = fdopen(yealinkDevice.socketFD, "r")) == NULL ){
			perror("fdopen");
			exit(1);
		}

		int claimed;
		if (yealinkDevice.options.halMode) {
			claimed =claimYealinkDevice( &yealinkDevice,
					yealinkDevice.usbBusNumber, yealinkDevice.usbDeviceNumber);

		} else {
			claimed=claimYealinkDevice( &yealinkDevice, SEARCH_IN_EVERY_BUS,
					PICK_ANY_DEVICE);

		}

		if (claimed != YEALINK_DEVICE_SUCESSIFULLY_INITIALIZED) {
			syslog(LOG_ERR, "USB device not found");
			fifoWrite( &yealinkDevice, "%s", FIFO_SAY_USB_NOT_FOUND);
			closeIO( &yealinkDevice);

			sleep(3);
			continue;
		}

		if (yealinkDevice.options.halMode) {
			// stop being root 
			setgid(65534);
			setuid(65534);
		}
		{
			char *name;
			yld_decode_model(yealinkDevice.deviceVersion, &name);
			syslog(LOG_ERR, "USB device %s [0x%x] initialized", name,
			yealinkDevice.deviceVersion);
			
			// i warn the client that the device is connected
			fifoWrite( &yealinkDevice,
				   FIFO_SAY_USB_DEVICE_FOUND,
				   yealinkDevice.usbBusNumber , 
				   yealinkDevice.usbDeviceNumber );
		}

		// start USB service thread
		// printf("BBBBB\n");
		if ((ret=pthread_create(&usb_server_thread, NULL,
				(void *)( void *) usbServer, (void *)&yealinkDevice ))!=0) {
			syslog(LOG_ERR, strerror(ret));
			//fprintf(stderr, "%s\n", strerror(ret));
			closeIO( &yealinkDevice);
			exit(1);
		}

		cmdServer_loop=ON;
		// MAIN LOOP (blocks process)
		cmdServer((void *)&yealinkDevice);

		usb_loop=OFF;

		pthread_join(usb_server_thread, NULL);

		releaseDevice( &yealinkDevice);

		closeIO( &yealinkDevice);

	} while (main_loop);

	if (yealinkDevice.socketName) {
		unlink(yealinkDevice.socketName);
	}
	return 0;
}
